#ifndef	__attr_buffer_mapped_H__
#define	__attr_buffer_mapped_H__

#include "rendPlatform.hpp"
#include "rendRegister.hpp"
#include "rendVectorArray.hpp"
#include "rendAttrBufferBasis.hpp"

#include <stdlib.h>
#include <memory.h>

#include <string>

namespace rend
{

////////////////////////////////////////////////////////////////////////////////////////////////
// mesh attribute buffer abstractor, user-space-mapping-enabled version
//
// provides:
// * container of mesh attribute data; keeps either vertex or index mesh attributes
// * data is passed to GL as AoS of customizable layout
// * lockless buffer interface
//
// requires:
// * user-space-mapping VBO API
////////////////////////////////////////////////////////////////////////////////////////////////

template < unsigned ATTR_CAPACITY_T = 16 >
class AttrBufferMapped
{
public:

	AttrBufferMapped();
	AttrBufferMapped(const AttrBufferMapped& src);				// copy ctor produces a blank GL slate

	~AttrBufferMapped();

	AttrBufferMapped& operator = (const AttrBufferMapped&);		// assignment op produces a blank GL slate

	////////////////////////////////////////////////////////////////////////////////////////////////
	// asset register support
	////////////////////////////////////////////////////////////////////////////////////////////////

	typedef naked::Register< std::string, AttrBufferMapped >	Register;

	static Register	s_default_register;

	////////////////////////////////////////////////////////////////////////////////////////////////
	// buffer type, length and content format
	////////////////////////////////////////////////////////////////////////////////////////////////

	enum BufferType
	{
		BUFFER_TYPE_NONE,
		BUFFER_TYPE_VERTEX,
		BUFFER_TYPE_INDEX
	};

	void					setBufferType(const BufferType type);
	BufferType				getBufferType() const;

	void					setBufferLength(const unsigned len);
	unsigned				getBufferLength() const;

	unsigned				getBufferRecSize() const;

	bool					setAttrScript(const unsigned attr_count, const AttrDescriptor* first);

	unsigned				getAttrCount() const;
	const AttrDescriptor&	getAttrDescriptor(const unsigned attr_idx) const;

	unsigned				getAttrOffset(const unsigned attr_idx) const;

	// attribute index seeker, returns -1U for failure
	unsigned				findAttr(const AttrDescriptor& desc) const;

	////////////////////////////////////////////////////////////////////////////////////////////////
	// buffer actuator; name supplied for reporting purposes
	////////////////////////////////////////////////////////////////////////////////////////////////
	bool					actuate(const std::string& name) const;

	////////////////////////////////////////////////////////////////////////////////////////////////
	// content access
	////////////////////////////////////////////////////////////////////////////////////////////////

	////////////////////////////////////////////////////////////////////////////////////////////////
	// attribute array mutator, carbon-copy version
	////////////////////////////////////////////////////////////////////////////////////////////////

	template < typename ELEMENT_TYPE_T, unsigned ELEMENT_COUNT_T >
	bool setAttr(
		const AttrDescriptor& desc,										// attribute descriptor
		const VectorArrayImm< ELEMENT_TYPE_T, ELEMENT_COUNT_T >& src);	// attribute array

	////////////////////////////////////////////////////////////////////////////////////////////////
	// attribute array accessor, carbon-copy version
	////////////////////////////////////////////////////////////////////////////////////////////////

	template < typename ELEMENT_TYPE_T, unsigned ELEMENT_COUNT_T >
	bool getAttr(
		const AttrDescriptor& desc,										// attribute descriptor
		VectorArray< ELEMENT_TYPE_T, ELEMENT_COUNT_T >& dst) const;		// attribute array

	////////////////////////////////////////////////////////////////////////////////////////////////
	// attribute array mutator, transfer-of-ownership, inbound version
	////////////////////////////////////////////////////////////////////////////////////////////////

	template < typename ELEMENT_TYPE_T, unsigned ELEMENT_COUNT_T >
	bool setAttrTransfer(
		const AttrDescriptor& desc,										// attribute descriptor
		VectorArray< ELEMENT_TYPE_T, ELEMENT_COUNT_T >& src);			// attribute array

	////////////////////////////////////////////////////////////////////////////////////////////////
	// attribute array mutator, transfer-of-ownership, outbound version
	////////////////////////////////////////////////////////////////////////////////////////////////

	template < typename ELEMENT_TYPE_T, unsigned ELEMENT_COUNT_T >
	bool getAttrTransfer(
		const AttrDescriptor& desc,										// attribute descriptor
		VectorArray< ELEMENT_TYPE_T, ELEMENT_COUNT_T >& dst);			// attribute array

private:

	BufferType				m_type;								// buffer type
	unsigned				m_rec_size;							// attribute record size as per script
	unsigned				m_len;								// buffer length, in attribute records

	unsigned				m_attr_count;						// attribute count
	AttrDescriptor			m_attr_desc[ATTR_CAPACITY_T];		// attribute script

	struct Attr
	{
		void*			bits;		// individual attribute's data
		unsigned		offset;		// attrbute offset in the vertex record
		mutable bool	dirty;		// attribute data modified flag
	};

	Attr					m_attr[ATTR_CAPACITY_T];			// attributes
	mutable GLuint			m_vbo;								// GL name
};


typedef AttrBufferMapped< 16 >	AttrBufferMapped16;				// attribute buffer of capacity 16 attributes


template < unsigned ATTR_CAPACITY_T >
inline AttrBufferMapped< ATTR_CAPACITY_T >::AttrBufferMapped()
: m_type(BUFFER_TYPE_NONE)
, m_rec_size(0)
, m_len(0)
, m_attr_count(0)
, m_vbo(0)
{
}


template < unsigned ATTR_CAPACITY_T >
inline AttrBufferMapped< ATTR_CAPACITY_T >::AttrBufferMapped(const AttrBufferMapped< ATTR_CAPACITY_T >& src)
: m_type(src.m_type)
, m_rec_size(src.m_rec_size)
, m_len(src.m_len)
, m_attr_count(src.m_attr_count)
, m_vbo(0)
{
	for (unsigned i = 0; i < m_attr_count; ++i)
	{
		m_attr_desc[i] = src.m_attr_desc[i];

		m_attr[i].offset = src.m_attr[i].offset;

		if (src.m_attr[i].bits)
		{
			const unsigned size = sizeofAttrElement(m_attr_desc[i].element_type) * m_attr_desc[i].element_count * m_len;

			m_attr[i].bits = malloc(size);
			assert(m_attr[i].bits);

			memcpy(m_attr[i].bits, src.m_attr[i].bits, size);

			m_attr[i].dirty = true;
		}
		else
		{
			m_attr[i].bits = NULL;
			m_attr[i].dirty = false;
		}
	}
}


template < unsigned ATTR_CAPACITY_T >
inline AttrBufferMapped< ATTR_CAPACITY_T >::~AttrBufferMapped()
{
	for (unsigned i = 0; i < m_attr_count; ++i)
		free(m_attr[i].bits);

	if (m_vbo)
	{
		printf("discarding vbo id 0x%08x\n", m_vbo);

		glDeleteBuffers(1, &m_vbo);

		if (reportGLError(stderr))
			fprintf(stderr, "failure deleting vbo\n");
	}
}


template < unsigned ATTR_CAPACITY_T >
inline AttrBufferMapped< ATTR_CAPACITY_T >& AttrBufferMapped< ATTR_CAPACITY_T >::operator = (
	const AttrBufferMapped< ATTR_CAPACITY_T >& src)
{
	this->~AttrBufferMapped();

	return *new (this) AttrBufferMapped< ATTR_CAPACITY_T >(src);
}


template < unsigned ATTR_CAPACITY_T >
inline void AttrBufferMapped< ATTR_CAPACITY_T >::setBufferType(const BufferType type)
{
	assert(BUFFER_TYPE_NONE == m_type);

	m_type = type;
}


template < unsigned ATTR_CAPACITY_T >
inline typename AttrBufferMapped< ATTR_CAPACITY_T >::BufferType AttrBufferMapped< ATTR_CAPACITY_T >::getBufferType() const
{
	return m_type;
}


template < unsigned ATTR_CAPACITY_T >
inline void AttrBufferMapped< ATTR_CAPACITY_T >::setBufferLength(const unsigned len)
{
	if (m_len == len)
		return;

	m_len = len;

	for (unsigned i = 0; i < m_attr_count; ++i)
	{
		free(m_attr[i].bits);

		m_attr[i].bits = NULL;
		m_attr[i].dirty = false;
	}
}


template < unsigned ATTR_CAPACITY_T >
inline unsigned AttrBufferMapped< ATTR_CAPACITY_T >::getBufferLength() const
{
	return m_len;
}


template < unsigned ATTR_CAPACITY_T >
template < typename ELEMENT_TYPE_T, unsigned ELEMENT_COUNT_T >
inline bool AttrBufferMapped< ATTR_CAPACITY_T >::setAttr(
	const AttrDescriptor& desc,
	const VectorArrayImm< ELEMENT_TYPE_T, ELEMENT_COUNT_T >& src)
{
	assert(sizeof(ELEMENT_TYPE_T) == sizeofAttrElement(desc.element_type));
	assert(ELEMENT_COUNT_T == desc.element_count);

	if (!m_len || src.count() != m_len)
		return false;

	unsigned idx;

	if (-1U == (idx = findAttr(desc)))
		return false;

	const unsigned size = sizeof(ELEMENT_TYPE_T[ELEMENT_COUNT_T]) * m_len;

	if (!m_attr[idx].bits)
		m_attr[idx].bits = malloc(size);

	assert(m_attr[idx].bits);

	memcpy(m_attr[idx].bits, &src.get(0), size);

	m_attr[idx].dirty = true;

	return true;
}


template < unsigned ATTR_CAPACITY_T >
template < typename ELEMENT_TYPE_T, unsigned ELEMENT_COUNT_T >
inline bool AttrBufferMapped< ATTR_CAPACITY_T >::getAttr(
	const AttrDescriptor& desc,
	VectorArray< ELEMENT_TYPE_T, ELEMENT_COUNT_T >& dst) const
{
	assert(sizeof(ELEMENT_TYPE_T) == sizeofAttrElement(desc.element_type));
	assert(ELEMENT_COUNT_T == desc.element_count);

	if (!m_len)
		return false;

	unsigned idx;

	if (-1U == (idx = findAttr(desc)))
		return false;

	if (m_attr[idx].bits)
	{
		const unsigned size = sizeof(ELEMENT_TYPE_T[ELEMENT_COUNT_T]) * m_len;

		void* copy = malloc(size);
		assert(copy);

		if (copy != NULL)
		{
			memcpy(copy, m_attr[idx].bits, size);

			dst.absorb(reinterpret_cast< ELEMENT_TYPE_T (*)[ELEMENT_COUNT_T] >(copy), m_len);

			return true;
		}
	}

	return false;
}


template < unsigned ATTR_CAPACITY_T >
template < typename ELEMENT_TYPE_T, unsigned ELEMENT_COUNT_T >
inline bool AttrBufferMapped< ATTR_CAPACITY_T >::setAttrTransfer(
	const AttrDescriptor& desc,
	VectorArray< ELEMENT_TYPE_T, ELEMENT_COUNT_T >& src)
{
	assert(sizeof(ELEMENT_TYPE_T) == sizeofAttrElement(desc.element_type));
	assert(ELEMENT_COUNT_T == desc.element_count);

	if (!m_len || src.count() != m_len)
		return false;

	unsigned idx;

	if (-1U == (idx = findAttr(desc)))
		return false;

	free(m_attr[idx].bits);

	unsigned dummy;
	src.drain(*reinterpret_cast< ELEMENT_TYPE_T (**)[ELEMENT_COUNT_T] >(&m_attr[idx].bits), dummy);

	m_attr[idx].dirty = true;

	return true;
}


template < unsigned ATTR_CAPACITY_T >
template < typename ELEMENT_TYPE_T, unsigned ELEMENT_COUNT_T >
inline bool AttrBufferMapped< ATTR_CAPACITY_T >::getAttrTransfer(
	const AttrDescriptor& desc,
	VectorArray< ELEMENT_TYPE_T, ELEMENT_COUNT_T >& dst)
{
	assert(sizeof(ELEMENT_TYPE_T) == sizeofAttrElement(desc.element_type));
	assert(ELEMENT_COUNT_T == desc.element_count);

	if (!m_len)
		return false;

	unsigned idx;

	if (-1U == (idx = findAttr(desc)))
		return false;

	if (m_attr[idx].bits)
	{
		dst.absorb(reinterpret_cast< ELEMENT_TYPE_T (*)[ELEMENT_COUNT_T] >(m_attr[idx].bits, m_len));

		m_attr[idx].bits = NULL;
		m_attr[idx].dirty = false;

		return true;
	}

	return false;
}


template < unsigned ATTR_CAPACITY_T >
inline bool AttrBufferMapped< ATTR_CAPACITY_T >::setAttrScript(
	const unsigned count,
	const AttrDescriptor* first)
{
	if (m_type == BUFFER_TYPE_NONE)
		return false;

	assert(m_type != BUFFER_TYPE_INDEX || count == 1);

	if (count > ATTR_CAPACITY_T)
		return false;

	if (count && first == NULL)
		return false;

	for (unsigned i = 0; i < m_attr_count; ++i)
		free(m_attr[i].bits);

	m_attr_count = count;
	m_rec_size = 0;

	for (unsigned i = 0; i < m_attr_count; ++i)
	{
		m_attr_desc[i] = first[i];

		m_attr[i].bits = NULL;
		m_attr[i].offset = m_rec_size;
		m_attr[i].dirty = false;

		m_rec_size += m_attr_desc[i].element_count * sizeofAttrElement(m_attr_desc[i].element_type);
	}

	return m_rec_size != 0;
}


template < unsigned ATTR_CAPACITY_T >
inline unsigned	AttrBufferMapped< ATTR_CAPACITY_T >::getAttrCount() const
{
	return m_attr_count;
}


template < unsigned ATTR_CAPACITY_T >
inline const AttrDescriptor& AttrBufferMapped< ATTR_CAPACITY_T >::getAttrDescriptor(
	const unsigned attr_idx) const
{
	assert(attr_idx < m_attr_count);

	return m_attr_desc[attr_idx];
}


template < unsigned ATTR_CAPACITY_T >
inline bool AttrBufferMapped< ATTR_CAPACITY_T >::actuate(
	const std::string& name) const
{
	if (BUFFER_TYPE_NONE == m_type)
		return false;

	if (!m_len || !m_rec_size)
		return false;

	bool some_dirty = false;
	bool all_dirty = true;

	for (unsigned i = 0; i < m_attr_count; ++i)
	{
		some_dirty |= m_attr[i].dirty;
		all_dirty &= m_attr[i].dirty;
	}

	if (!m_vbo)
	{
		glGenBuffers(1, &m_vbo);

		if (!m_vbo)
		{
			fprintf(stderr, "error: failed to obtain vbo name for '%s'\n", name.c_str());
			return false;
		}

		printf("vbo id 0x%08x '%s'\n", m_vbo, name.c_str());
	}

	switch (m_type)
	{
	case BUFFER_TYPE_VERTEX:

		glBindBuffer(GL_ARRAY_BUFFER, m_vbo);

		if (some_dirty)
		{
			if (all_dirty)
				glBufferData(GL_ARRAY_BUFFER, m_len * m_rec_size, 0, GL_DYNAMIC_DRAW);

			void* const mapped = glMapBufferOES(GL_ARRAY_BUFFER, GL_WRITE_ONLY_OES);

			if (mapped)
			{
				for (unsigned i = 0; i < m_attr_count; ++i)
				{
					const unsigned attr_size = m_attr_desc[i].element_count * sizeofAttrElement(m_attr_desc[i].element_type);

					if (m_attr[i].dirty)
					{
						const unsigned offset = m_attr[i].offset;
						const void* attr_bits = m_attr[i].bits;

						for (unsigned j = 0; j < m_len; ++j)
							memcpy(
								reinterpret_cast< int8* >(mapped) + j * m_rec_size + offset,
								reinterpret_cast< int8* >(attr_bits) + j * attr_size,
								attr_size);

						m_attr[i].dirty = false;
					}
				}

				glUnmapBufferOES(GL_ARRAY_BUFFER);
			}
			else
				fprintf(stderr, "error: failed to map vbo to user space for '%s'\n", name.c_str());
		}

		break;

	case BUFFER_TYPE_INDEX:

		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_vbo);

		if (some_dirty)
		{
			// we never partially update the index buffer - no need to do user-space mapping

			glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_len * m_rec_size, m_attr[0].bits, GL_STATIC_DRAW);
			m_attr[0].dirty = false;
		}

		break;
	}

	return !reportGLError(stderr);
}

} // namespace rend

#endif // __attr_buffer_mapped_H__
